﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using ReactiveGraph.Core.Extensions;

namespace ReactiveGraph.Core
{
    public class ReactiveNode : IReactiveNode
    {
        private readonly ObservableCollection<IObservable<Unit>> _behaviours =
            new ObservableCollection<IObservable<Unit>>();

        private readonly ReactiveNodeList _childNodes;
        private readonly string _name;
        private readonly Subject<NodeEvent> _nodeEvents = new Subject<NodeEvent>();
        private readonly Dictionary<string, IProperty> _properties = new Dictionary<string, IProperty>();

        private IReactiveNode _conceptualIdentity;

        private IObservable<NodeEvent> _events;


        public ReactiveNode(
            Func<IReactiveNode, string> nodeName,
            ReactiveNodeList childNodes
            )
        {
            _name = nodeName(this);
            _conceptualIdentity = this;
            _childNodes = childNodes;
        }

        #region IReactiveNode Members

        public void AttachProperty(IProperty property)
        {
            _properties.Add(property.Name, property);
            property.Initialize(_conceptualIdentity, _nodeEvents);
        }

        public IEnumerable<IProperty> Properties
        {
            get { return _properties.Values; }
        }

        public IObservable<NodeEvent> Events
        {
            get
            {
                if (_events == null)
                    _events = CreateEventsObservable();
                return _events;
            }
        }

        public IObservable<Unit> Behaviours
        {
            get
            {
                var myBehaviours = _behaviours.WhileInCollection(observable => observable);
                var myChildBehaviours = ChildNodes.WhileInCollection(node => node.Behaviours);
                return myBehaviours.Merge(myChildBehaviours);
            }
        }

        public bool HasProperty(string name)
        {
            return _properties.ContainsKey(name);
        }

        public void AttachBehaviour(IObservable<Unit> behaviour)
        {
            _behaviours.Add(behaviour);
        }

        public void DetachBehaviour(IObservable<Unit> behaviour)
        {
            _behaviours.Remove(behaviour);
        }

        public IEnumerable<IProperty> GetDescendantProperties()
        {
            return _properties.Values
                .Concat(ChildNodes.SelectMany(x => x.GetDescendantProperties()));
        }

        public string NodeName
        {
            get { return _name; }
        }

        public IProperty GetProperty(string name)
        {
            return _properties[name];
        }


        public ReactiveNodeList ChildNodes
        {
            get { return _childNodes; }
        }

        #endregion

        private IObservable<NodeEvent> CreateEventsObservable()
        {
            var childNodeEvents = _childNodes.WhileInCollection(node => node.Events.Select(Wrap));
            var childStructureChange = _childNodes.Changed.SelectMany(CreateStructureEvents);
            var all = _nodeEvents
                .Merge(childNodeEvents)
                .Merge(childStructureChange);
            return all.Publish().RefCount();
        }

        private IEnumerable<NodeEvent> CreateStructureEvents(ChangedArgs<IReactiveNode> changedArgs)
        {
            foreach (var added in changedArgs.AddedItems)
            {
                yield return NodeEvent.NodeAdded(added, _conceptualIdentity);
            }
            foreach (var r in changedArgs.RemovedItems)
            {
                yield return NodeEvent.NodeRemoved(r, _conceptualIdentity);
            }
        }

        private NodeEvent Wrap(NodeEvent nodeEvent)
        {
            return NodeEvent.Wrap(nodeEvent, _conceptualIdentity);
        }

        public override string ToString()
        {
            return NodeName;
        }

        public void SetConceptualIdentity(IReactiveNode node)
        {
            _conceptualIdentity = node;
        }
    }
}